/* @flow */

import { _Vue } from '../install'
import type Router from '../index'
import { inBrowser } from '../util/dom'
import { runQueue } from '../util/async'
import { warn } from '../util/warn'
import { START, isSameRoute, handleRouteEntered } from '../util/route'
import {
  flatten,
  flatMapComponents,
  resolveAsyncComponents,
} from '../util/resolve-components'
import {
  createNavigationDuplicatedError,
  createNavigationCancelledError,
  createNavigationRedirectedError,
  createNavigationAbortedError,
  isError,
  isNavigationFailure,
  NavigationFailureType,
} from '../util/errors'
import { handleScroll } from '../util/scroll'

export class History {
  // router实例
  router: Router
  // 基路径
  base: string
  // 当前路由
  current: Route
  // 当前正在导航的路由
  pending: ?Route
  // 更新根组件的_route属性，即当前路由
  cb: (r: Route) => void
  // ready状态
  ready: boolean
  // onReady注册的第一个函数
  readyCbs: Array<Function>
  // onReady注册的第二个函数
  readyErrorCbs: Array<Function>
  // onError注册的函数
  errorCbs: Array<Function>
  listeners: Array<Function>
  cleanupListeners: Function

  // implemented by sub-classes
  // 子类继承时需要实现的方法
  // +go: (n: number) => void;
  // +push: (loc: RawLocation, onComplete?: Function, onAbort?: Function) => void;
  // +replace: (
  //   loc: RawLocation,
  //   onComplete?: Function,
  //   onAbort?: Function
  // ) => void;
  // +ensureURL: (push?: boolean) => void;
  // +getCurrentLocation: () => string;
  // +setupListeners: Function

  constructor(router: Router, base: ?string) {
    this.router = router
    this.base = normalizeBase(base)
    // start with a route object that stands for "nowhere"
    this.current = START
    this.pending = null
    this.ready = false
    this.readyCbs = []
    this.readyErrorCbs = []
    this.errorCbs = []
    this.listeners = []
  }
  // 注册路由改变时执行的回调函数
  // router.js 170行
  listen(cb: Function) {
    this.cb = cb
  }
  // onReady API
  onReady(cb: Function, errorCb: ?Function) {
    if (this.ready) {
      cb()
    } else {
      this.readyCbs.push(cb)
      if (errorCb) {
        this.readyErrorCbs.push(errorCb)
      }
    }
  }
  // onError API
  onError(errorCb: Function) {
    this.errorCbs.push(errorCb)
  }
  // 路由跳转
  transitionTo(
    location: RawLocation,
    onComplete?: Function,
    onAbort?: Function
  ) {
    let route
    // catch redirect option https://github.com/vuejs/vue-router/issues/3201
    try {
      // 获取即将要跳转的路由对象
      route = this.router.match(location, this.current)
    } catch (e) {
      this.errorCbs.forEach((cb) => {
        cb(e)
      })
      // Exception should still be thrown
      throw e
    }
    const prev = this.current
    // 确认过渡，真正的跳转
    this.confirmTransition(
      route,
      () => {
        // 更新当前路由
        this.updateRoute(route)
        // 跳转成功的回调函数
        onComplete && onComplete(route)
        // 确保浏览器显示的URL和route的route的path保持一致
        this.ensureURL()
        // afterEach钩子函数
        this.router.afterHooks.forEach((hook) => {
          hook && hook(route, prev)
        })
        // onReady钩子函数
        // fire ready cbs once
        if (!this.ready) {
          this.ready = true
          this.readyCbs.forEach((cb) => {
            cb(route)
          })
        }
      },
      (err) => {
        if (onAbort) {
          onAbort(err)
        }
        if (err && !this.ready) {
          // Initial redirection should not mark the history as ready yet
          // because it's triggered by the redirection instead
          // https://github.com/vuejs/vue-router/issues/3225
          // https://github.com/vuejs/vue-router/issues/3331
          if (
            !isNavigationFailure(err, NavigationFailureType.redirected) ||
            prev !== START
          ) {
            this.ready = true
            this.readyErrorCbs.forEach((cb) => {
              cb(err)
            })
          }
        }
      }
    )
  }
  // 确认过渡，真正的跳转
  confirmTransition(route: Route, onComplete: Function, onAbort?: Function) {
    const current = this.current
    this.pending = route
    // 封装取消处理函数
    const abort = (err) => {
      // changed after adding errors with
      // https://github.com/vuejs/vue-router/pull/3047 before that change,
      // redirect and aborted navigation would produce an err == null
      if (!isNavigationFailure(err) && isError(err)) {
        if (this.errorCbs.length) {
          this.errorCbs.forEach((cb) => {
            cb(err)
          })
        } else {
          if (process.env.NODE_ENV !== 'production') {
            warn(false, 'uncaught error during route navigation:')
          }
          console.error(err)
        }
      }
      onAbort && onAbort(err)
    }
    const lastRouteIndex = route.matched.length - 1
    const lastCurrentIndex = current.matched.length - 1
    // 相同的路由
    if (
      isSameRoute(route, current) &&
      // in the case the route map has been dynamically appended to
      lastRouteIndex === lastCurrentIndex &&
      // matched是一个数组，包括从根路由到目标路由的所有匹配路由
      route.matched[lastRouteIndex] === current.matched[lastCurrentIndex]
    ) {
      this.ensureURL()
      if (route.hash) {
        handleScroll(this.router, current, route, false)
      }
      // createNavigationDuplicatedError，创建路由导航故障<跳转到当前路由>
      return abort(createNavigationDuplicatedError(current, route))
    }
    /**
     * 交叉比对当前路由的路由记录和现在的这个路由的路由记录
     * 以便能准确得到父子路由更新的情况下可以确切的知道
     * 哪些组件需要更新 哪些不需要更新
     */
    /**
     * updated：需要更新的路由
     * deactivated: 失活的路由
     * actived: 新激活的路由
     */
    const { updated, deactivated, activated } = resolveQueue(
      this.current.matched,
      route.matched
    )
    // 路由导航钩子函数
    const queue: Array<?NavigationGuard> = [].concat(
      // in-component leave guards
      extractLeaveGuards(deactivated), // 组件beforeRouteLeave
      // global before hooks
      this.router.beforeHooks, // 全局beforeEach
      // in-component update hooks
      extractUpdateHooks(updated), // 组件beforeRouteUpdate
      // in-config enter guards
      activated.map((m) => m.beforeEnter), // 路由配置beforeEnter
      // async components
      resolveAsyncComponents(activated) // 解析异步路由组件
    )
    // 路由首航钩子函数遍历器
    const iterator = (hook: NavigationGuard, next) => {
      // 确保执行期间还是当前路由
      if (this.pending !== route) {
        return abort(createNavigationCancelledError(current, route))
      }
      try {
        hook(
          route,
          current,
          /** next方法 */
          (to: any) => {
            // 上一个钩子函数返回false
            if (to === false) {
              // next(false) -> abort navigation, ensure current URL
              this.ensureURL(true)
              abort(createNavigationAbortedError(current, route))
            }
            // 上一个钩子函数返回false，
            else if (isError(to)) {
              this.ensureURL(true)
              abort(to)
            }
            // next()跳转路由
            else if (
              typeof to === 'string' ||
              (typeof to === 'object' &&
                (typeof to.path === 'string' || typeof to.name === 'string'))
            ) {
              // next('/') or next({ path: '/' }) -> redirect
              abort(createNavigationRedirectedError(current, route))
              if (typeof to === 'object' && to.replace) {
                this.replace(to)
              } else {
                this.push(to)
              }
            } else {
              // 确认导航，这里的参数to没啥用的
              // confirm transition and pass on the value
              next(to)
            }
          }
        )
      } catch (e) {
        abort(e)
      }
    }
    // 执行首航钩子函数
    // runQueue函数会依次调用iterator，并自动化next()函数的流程
    runQueue(queue, iterator, () => {
      // wait until async components are resolved before
      // extracting in-component enter guards
      // 收集激活组件的beforeRouteEnter钩子函数
      const enterGuards = extractEnterGuards(activated)
      // 收集全局的beforeResolve钩子函数
      const queue = enterGuards.concat(this.router.resolveHooks)
      // 再执行一次runQueue，刷新beforeRouteEnter和beforeResolve
      runQueue(queue, iterator, () => {
        if (this.pending !== route) {
          return abort(createNavigationCancelledError(current, route))
        }
        this.pending = null
        // 这里会执行全局的afterEach
        onComplete(route)
        // 执行beforeRouteEnter中，传给next的回调函数
        if (this.router.app) {
          this.router.app.$nextTick(() => {
            handleRouteEntered(route)
          })
        }
      })
    })
  }
  // 更新当前路由
  updateRoute(route: Route) {
    this.current = route
    this.cb && this.cb(route)
  }

  setupListeners() {
    // Default implementation is empty
  }

  teardown() {
    // clean up event listeners
    // https://github.com/vuejs/vue-router/issues/2341
    this.listeners.forEach((cleanupListener) => {
      cleanupListener()
    })
    this.listeners = []

    // reset current history route
    // https://github.com/vuejs/vue-router/issues/3294
    this.current = START
    this.pending = null
  }
}
// 格式化base
function normalizeBase(base: ?string): string {
  if (!base) {
    if (inBrowser) {
      // respect <base> tag
      // 判断是否有<base href="/base" />标签
      const baseEl = document.querySelector('base')
      base = (baseEl && baseEl.getAttribute('href')) || '/'
      // strip full URL origin
      base = base.replace(/^https?:\/\/[^\/]+/, '')
    } else {
      // 默认值
      base = '/'
    }
  }
  // make sure there's the starting slash
  // base: "basePath" => "/basePath"
  if (base.charAt(0) !== '/') {
    base = '/' + base
  }
  // base: "basePath/" => "/basePath"
  // remove trailing slash
  return base.replace(/\/$/, '')
}
// 对比当前路由的matched和跳转路由的matched
// 找出需要更新、新激活、失活的路由
function resolveQueue(
  current: Array<RouteRecord>,
  next: Array<RouteRecord>
): {
  updated: Array<RouteRecord>,
  activated: Array<RouteRecord>,
  deactivated: Array<RouteRecord>,
} {
  let i
  const max = Math.max(current.length, next.length)
  for (i = 0; i < max; i++) {
    if (current[i] !== next[i]) {
      break
    }
  }
  return {
    updated: next.slice(0, i), // 需要更新的
    activated: next.slice(i), // 新激活的路由
    deactivated: current.slice(i), // 失活的路由
  }
}
// 提取路由的守卫
function extractGuards(
  records: Array<RouteRecord>, // 路由数组
  name: string, // 路由守卫名称
  bind: Function, // 绑定函数
  reverse?: boolean // 是否逆序执行
): Array<?Function> {
  // 遍历records的matched数组，提取路由守卫
  /**
   * def: 组件定义
   * instance: 组件实例
   * match: 路由的matched数组
   * key: 视图名
   */
  const guards = flatMapComponents(records, (def, instance, match, key) => {
    const guard = extractGuard(def, name)
    if (guard) {
      return Array.isArray(guard)
        ? guard.map((guard) => bind(guard, instance, match, key))
        : bind(guard, instance, match, key)
    }
  })
  return flatten(reverse ? guards.reverse() : guards)
}
// 提取组件定义的守卫
function extractGuard(
  def: Object | Function, // 组件定义
  key: string // 路由守卫名称
): NavigationGuard | Array<NavigationGuard> {
  if (typeof def !== 'function') {
    // extend now so that global mixins are applied.
    def = _Vue.extend(def)
  }
  return def.options[key]
}
// 提取beforeRouteLeave守卫
function extractLeaveGuards(deactivated: Array<RouteRecord>): Array<?Function> {
  return extractGuards(deactivated, 'beforeRouteLeave', bindGuard, true)
}
// 提取beforeRouteUpdate守卫
function extractUpdateHooks(updated: Array<RouteRecord>): Array<?Function> {
  return extractGuards(updated, 'beforeRouteUpdate', bindGuard)
}
// 路由守卫绑定Vue实例
function bindGuard(guard: NavigationGuard, instance: ?_Vue): ?NavigationGuard {
  if (instance) {
    return function boundRouteGuard() {
      return guard.apply(instance, arguments)
    }
  }
}
// 提取beforeRouteEnter守卫
function extractEnterGuards(activated: Array<RouteRecord>): Array<?Function> {
  return extractGuards(
    activated,
    'beforeRouteEnter',
    (guard, _, match, key) => {
      return bindEnterGuard(guard, match, key)
    }
  )
}
// beforeRouteEnter守卫绑定
function bindEnterGuard(
  guard: NavigationGuard,
  match: RouteRecord,
  key: string
): NavigationGuard {
  return function routeEnterGuard(to, from, next) {
    return guard(to, from, (cb) => {
      if (typeof cb === 'function') {
        if (!match.enteredCbs[key]) {
          match.enteredCbs[key] = []
        }
        match.enteredCbs[key].push(cb)
      }
      next(cb)
    })
  }
}
